package jn;
import robocode.*;
import java.awt.Color;
import java.awt.geom.Point2D;

/**
 * Spike2 - a robot by Joel Neely
 */
public class Spike2 extends AdvancedRobot {

	private static final int SPIN_LIMIT = 4;
	private static final double MOVEMENT_STEP = 24.0D;
	private static final int SHOT_BURST_LIMIT = 3;

	private class Battlefield {

		private static final double MARGIN = 64.0D;

		private final double W;
		private final double H;
		private final double WMIN = MARGIN;
		private final double WMAX;
		private final double HMIN = MARGIN;
		private final double HMAX;

		public final double D;
		public final Point2D.Double CENTER;

		private Point2D.Double[] goals;
		private int goalIndex;

		public Battlefield() {
			W = getBattleFieldWidth();
			H = getBattleFieldHeight();
			D = Math.hypot(W, H);
			CENTER = new Point2D.Double(W / 2.0, H / 2.0);
			WMAX = W - MARGIN;
			HMAX = H - MARGIN;
			initializeGoals();
		}

		private void initializeGoals() {
			goals = new Point2D.Double[] {
				new Point2D.Double(WMIN, HMIN),
				new Point2D.Double(WMIN, HMAX),
				new Point2D.Double(WMAX, HMAX),
				new Point2D.Double(WMAX, HMIN),
			};
			goalIndex = 0;
		}

		public Point2D.Double getCurrentGoal() {
			return goals[goalIndex];
		}

		public Point2D.Double getNextGoal() {
			goalIndex = (goalIndex + 1) % goals.length;
			return goals[goalIndex];
		}

	}

	private Battlefield BF;
	private Point2D.Double goal;

	private int shotBurstCount;

	private int others;

	/**
	 * run: Spike2's default behavior
	 */
	public void run() {
		BF = new Battlefield();
		goal = BF.getCurrentGoal();

		setColors(new Color(153, 0, 0), new Color(153, 0, 153), new Color(153, 153, 0));

		while(true) {
			others = getOthers();
			shotBurstCount = 0;
			if (others < SPIN_LIMIT) {
				setMaxVelocity(8);
				setAdjustGunForRobotTurn(true);
				reAimGun();
				if (getDistanceRemaining() < 10.0D || distanceTo(goal) < MOVEMENT_STEP) {
					goal = BF.getNextGoal();
				}
				goTo(goal);
				execute();
			} else {
				setAdjustGunForRobotTurn(false);
				setMaxVelocity(5);
				setTurnRight(10000);
				ahead(1000);
			}
		}
	}

	private void reAimGun() {
		double delta = normalRelativeAngle(getHeading() - getGunHeading() + 90.0D);
		setTurnGunRight(delta);
	}

	/**
	 * onScannedRobot: What to do when you see another robot
	 */
	public void onScannedRobot(ScannedRobotEvent e) {
		if (others < SPIN_LIMIT) {
			double distance = e.getDistance() / BF.D;
			if (distance < 0.5) {
				double power = 1.0 / (10.0D * distance + 0.2D);
				shoot(normalRelativeAngle(e.getBearing() + (getHeading() - getRadarHeading())), power);
			}
		} else {
			fire (3.0D);
		}
	}

	/**
	 * onHitRobot
	 */	
	public void onHitRobot(HitRobotEvent e) {
		double bearing = e.getBearing();
		if (-10.0D < bearing && bearing < 10.0D) {
			fire(3);
			if (e.isMyFault()) {
				turnRight(10);
			}
		} else {
			shoot(normalRelativeAngle(e.getBearing() + (getHeading() - getRadarHeading())), 3.0D);
		}
	}

	private void shoot(double gunTurnAmount, double power) {
		if (Math.abs(gunTurnAmount) < 5) {
			fire(power);
			if (SHOT_BURST_LIMIT < ++shotBurstCount) {
				scan();
			}
		} else {
			setTurnGunRight(gunTurnAmount);
		}
	}

	/**
	 * onHitByBullet: What to do when you're hit by a bullet
	 */
	public void onHitByBullet(HitByBulletEvent e) {
		if (others < SPIN_LIMIT) {
			goal = BF.getNextGoal();
			goTo(goal);
		}
	}

	public void onHitWall(HitWallEvent event) {
		stop(true);
		turnRight(event.getBearing());
		execute();
	}

	private double distance(Point2D.Double a, Point2D.Double b) {
		return a.distance(b);
	}

	private double distanceTo(Point2D.Double target) {
		return target.distance(getX(), getY());
	}

	void goTo(Point2D.Double there) {
	    double angle = normalAbsoluteHeading(
			Math.toDegrees(Math.PI/2 - Math.atan2(getY() - there.y, getX() - there.x))
		);
		int dir=1;
		angle = normalRelativeAngle(getHeading() - angle + 180);
		setTurnLeft(angle);
		setAhead(distanceTo(there));
	}

	private double normalAbsoluteHeading(double angle) {
		if (angle < 0)
			return 360 + (angle % 360);
		else
			return angle % 360;
	}

	private double normalRelativeAngle(double angle) {
		if (angle > 180)
			return ((angle + 180) % 360) - 180;
		if (angle < -180)
			return ((angle - 180) % 360) + 180;
		return angle;
	}

}
